// https://blog.csdn.net/rxbook/article/details/130654337
package main

import (
	"errors"
	"fmt"
)

var (
	ErrDbConnectFailed = errors.New("数据库连接失败")
	ErrDbUsernameError = errors.New("数据库用户名错误")
	ErrDbPasswordError = errors.New("数据库密码错误")
	ErrCodeTest        = fmt.Errorf("code:%d", 200)
)

func main() {
	fmt.Println(ErrCodeTest)               //code:200
	fmt.Printf("%T\n", ErrCodeTest)        //*errors.errorString
	fmt.Printf("%T\n", ErrDbConnectFailed) //*errors.errorString
	println()
	if err := checkAge(-1); err != nil {
		fmt.Println(err) //年龄是：-1,不合法
		//return
	}

	fmt.Println(getDbInfo())
	updateDb()
	updateDbV2()
	errorIsDemo()
	println()
	errorAsDemo()

	if res, err := add(500); err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(res)
	}

	res, err := add(500)
	if e, ok := err.(*MyError); ok {
		fmt.Println("错误码是 ", e.errCode, "错误内容是 ", e.errMsg)
	} else {
		fmt.Println(res)
	}
}

// 自定义一个error结构体
type MyError struct {
	errCode int    //错误码
	errMsg  string //错误信息
}

func (e *MyError) Error() string {
	return fmt.Sprintf("错误码:%d,错误信息:%v", e.errCode, e.errMsg)
}

func add(code int) (int, error) {
	if code == 500 {
		// return 0, errors.New("服务器出问题了")
		return 0, &MyError{
			errCode: 500,
			errMsg:  "服务器出问题了",
		}
	} else {
		return 1, nil
	}
}

func errorAsDemo() {
	var err = &MyError{errCode: 500, errMsg: "服务器异常"}
	err1 := fmt.Errorf("err1: %w", err)
	err2 := fmt.Errorf("err2: %w", err1)
	fmt.Println(err, " -- ", err1, "--", err2) //错误码:500,错误信息:服务器异常  --  err1: 错误码:500,错误信息:服务器异常 -- err2: err1: 错误码:500,错误信息:服务器异常

	var e *MyError
	fmt.Println(errors.As(err2, &e))    //true
	fmt.Println(errors.As(err1, &e))    //true
	fmt.Println(errors.As(err2, &err1)) //true
	fmt.Println(errors.As(err1, &err2)) //true
}

// 使用 errors.Is 可以根据错误链找到最底层的错误信息，并判断它们是否是同一类
func errorIsDemo() {
	//使用 fmt.Errorf 对错误变量包装
	err1 := fmt.Errorf("err1: %w", ErrDbConnectFailed) //基于 ErrDbConnectFailed 包装出 err1
	err2 := fmt.Errorf("err2: %w", err1)               //基于 err1 包装出 err2
	fmt.Println(err1)                                  //输出: err1: 数据库连接失败
	fmt.Println(err2)                                  //输出: err2: err1: 数据库连接失败
	fmt.Println(err2 == ErrDbConnectFailed)            //false
	fmt.Println(errors.Is(err1, ErrDbConnectFailed))   //true
	fmt.Println(errors.Is(err2, ErrDbConnectFailed))   //true
	fmt.Println(errors.Is(err2, err1))                 //true
	fmt.Println(errors.Is(err1, err2))                 //false

	//可以使用 errors.Unwrap 获取被嵌套的 error
	fmt.Println(errors.Unwrap(err1)) //数据库连接失败
	fmt.Println(errors.Unwrap(err2)) //err1: 数据库连接失败
}

// 从 Go 1.13 版本开始，标准库 errors 包提供了 Is 函数用于错误处理方对错误值的检视
func updateDbV2() {
	err := checkDbConnect()
	if errors.Is(err, ErrDbConnectFailed) {
		fmt.Println("处理数据库连接失败的逻辑...")
		return
	}
	if errors.Is(err, ErrDbUsernameError) {
		fmt.Println("处理数据库用户名错误的逻辑...")
		return
	}
	if errors.Is(err, ErrDbPasswordError) {
		fmt.Println("处理数据库密码错误的逻辑...")
		return
	}
	fmt.Println("操作处理完成")
}

// 判断具体的错误类型再处理相关逻辑
func updateDb() {
	err := checkDbConnect()
	if err != nil {
		switch err {
		case ErrDbConnectFailed:
			fmt.Println("处理数据库连接失败的逻辑...")
			return
		case ErrDbUsernameError:
			fmt.Println("处理数据库用户名错误的逻辑...")
			return
		case ErrDbPasswordError:
			fmt.Println("处理数据库密码错误的逻辑...")
			return
		}
	}
	fmt.Println("操作处理完成")
}

// 不关心返回错误值携带的具体上下文信息，只要发生错误就返回
func getDbInfo() (string, error) {
	err := checkDbConnect()
	if err != nil {
		return "", err
	}
	return "mysql", nil
}

func checkDbConnect() error {
	foo := false
	if !foo {
		//return errors.New("数据库连接失败")
		return ErrDbConnectFailed
	}
	return nil
}

// 验证年龄是否合法
func checkAge(age int) error {
	if age < 0 {
		//return errors.New("年龄不合法") //返回error对象
		return fmt.Errorf("年龄是：%d,不合法", age)
	}
	fmt.Println("年龄是：", age)
	return nil
}
